package evolution;

import ca.swank.hardquor.*;
import ca.swank.hq.ai.CachingSmartBoard;
import ca.swank.hq.ai.hqSimpleAIGame;
import java.util.ArrayList;
import java.util.Collections;

/**
 * 
 * @author Alon Segal
 * 
 */
public class GenAIGame extends hqSimpleAIGame {
	private static final int POPULATION = 500;
	private static final int GENERATIONS = 10;
	private static final int INDIVIDUAL_SIZE = 2;

	private String playerType;
	private double pMutate = 0.2;

	public GenAIGame(hardquorGameClient gameClient, int me, String userName) {
		super(gameClient, me, userName);
	}

	public GenAIGame(hardquorGameClient gameClient, int playerNumber,
			String userName, int selection, double pMut) {
		this(gameClient, playerNumber, userName);

		this.pMutate = pMut;
		
		if (selection == 1)
			playerType = "FITNESS";
		if (selection == 2)
			playerType = "TOURNAMENT";
		if (selection == 3)
			playerType = "RANK";
	}

	/**
	 * notification that a move required by the current player.
	 * 
	 * The player initially speaks a comment to the board, and then examines the
	 * board.
	 */
	public void notifyTurn() {
		//System.out.println(playerType + " turn.");
		
		Move m = this.evaluateMove();
		
		if (m instanceof WalkMove) {
			gameClient.tryMove(((WalkMove)m).getSource(), ((WalkMove)m).getTarget());
		}
		if (m instanceof WallMove) {
			gameClient.tryMove(((WallMove)m).a1(), ((WallMove)m).a2(), ((WallMove)m).b1(), ((WallMove)m).b2());
		}
	}

	private Move evaluateMove() {
		// The best individual of every generation (no need to store the whole generation) 
		Individual[] bestIndividuals = new Individual[GENERATIONS];
		
		// Generate the generation_0
		Individual[] population = new Individual[POPULATION];
		
		for (int i = 0; i < POPULATION; i++) {
			Individual in = new Individual(INDIVIDUAL_SIZE, board.getTurn());
			in.generate((CachingSmartBoard) board.clone());
			population[i] = in;
		}
		
		ReservationTree rt = new ReservationTree();
		for (int i = 0; i < POPULATION; i++) {
			rt.addIndividual(population[i]);
		}
		rt.calcMinMax(new BFSBoardEvaluator(), board);
		ArrayList<Individual> individualPool = rt.GenerateIndividualsFromTree();
		
		bestIndividuals[0] = (Individual)this.getBestIndividual(individualPool).clone();

		for (int i = 1; i < GENERATIONS; i++) {
			Individual[] currentPop = this.createNextGen(population);
			population = currentPop;
			Individual best = this.getBestIndividual(population);
			bestIndividuals[i] = best;
		}
		
		//Choose the best of the best
		Individual chosenOne = this.getBestIndividual(bestIndividuals);
		
		/*** Print situation ***/
		CachingSmartBoard b = (CachingSmartBoard)this.board.clone();
		b.makeMove(chosenOne.getMove());
		System.out.println("MOVE IS "+chosenOne.getMove().toString() + "With fitness " +
				chosenOne.getFitness());
		System.out.println(b);
		if (b.getWinner() != hardquorBoard.PLAYER_NONE) {
			System.out.println("WINNER IS "+ (b.getWinner()+1));
		}
		/*** Print situation ***/
		
		return chosenOne.getMove();
	}
	
	private Individual[] createNextGen(Individual[] pop) {
		Individual[] newPop = new Individual[POPULATION];
		
		for(Individual i : pop) {
			double p = Math.random();
			if (p <= pMutate) {
				i.mutate();
			}
		}
		
		ReservationTree rt = new ReservationTree();
		for (int i = 0; i < POPULATION; i++) {
			rt.addIndividual(pop[i]);
		}
		rt.calcMinMax(new BFSBoardEvaluator(), board);
		ArrayList<Individual> individualPool = rt.GenerateIndividualsFromTree();
		
		for (int i = 0; i < POPULATION; i++) {
			//Create new generation
			Individual selected = null;
			if (playerType.equals("FITNESS")) {
				selected = this.totalFitnessSelection(individualPool);
			}
			if (playerType.equals("TOURNAMENT")) {
				selected = this.tournamentSelection(individualPool);
			}
			if (playerType.equals("RANK")) {
				selected = this.rankBasedSelection(individualPool);
			}
			
			newPop[i] = selected;
		}
		
		
		return newPop;
	}
	
	private Individual getBestIndividual(Individual[] pop) {
		Individual res = pop[0];
		IndividualComparator c = new IndividualComparator();
		for(int i = 1; i < pop.length; i++) {
			if (c.compare(pop[i], res) > 0) {
				res = pop[i];
			}
		}
		
		return res;
	}
	
	private Individual getBestIndividual(ArrayList<Individual> pop) {
		Individual res = pop.get(pop.size() - 1);
		IndividualComparator c = new IndividualComparator();
		for(int i = pop.size() - 1; i >= 0 ; i--) {
			if (c.compare(pop.get(i), res) > 0) {
				res = pop.get(i);
			}
		}
		
		return res;
	}

	private Individual totalFitnessSelection(ArrayList<Individual> population) {
		double totalFitness = 0;
		for (Individual i : population) {
			totalFitness += i.getFitness();
		}

		double random = ((double) Math.random() * (double) totalFitness);
		int i = 0;
		do {
			random -= population.get(i).getFitness();
			i++;
		} while (random > 0);
		return population.get(i-1);
	}

	private Individual tournamentSelection(ArrayList<Individual> population) {
		while (true) {
			int random1 = (int) ((double) Math.random() * (double) population.size());
			int random2 = (int) ((double) Math.random() * (double) population.size());
			Individual i1 = population.get(random1);
			Individual i2 = population.get(random2);
			if (i1.getFitness() == i2.getFitness()) {
				return i1;
			}
			return (i1.getFitness() > i2.getFitness()) ? i1 : i2;
		}
	}

	private Individual rankBasedSelection(ArrayList<Individual> population) {
		IndividualComparator c = new IndividualComparator();

		// Sort the population and each individual's rank will be index+1
		Collections.sort(population, c);

		double totalRanks = ((population.size() + 1) * (population.size())) / 2;

		int random = (int) ((double) Math.random() * (double) (totalRanks + 1));
		int i = 1;
		do {
			random -= i;
			i++;
		} while (random > 0);
		return population.get(i - 2);
	}
}